// Copyright (c) 2022. Eritque arcus and contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or any later version(in your opinion).
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#pragma once

#include <cstddef>
#include <string>  // string
#include <utility> // move
#include <vector>  // vector

#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/string_concat.hpp>

NLOHMANN_JSON_NAMESPACE_BEGIN

/*!
@brief SAX interface

This class describes the SAX interface used by @ref nlohmann::json::sax_parse.
Each function is called in different situations while the input is parsed. The
boolean return value informs the parser whether to continue processing the
input.
*/
template<typename BasicJsonType>
struct json_sax {
    using number_integer_t = typename BasicJsonType::number_integer_t;
    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
    using number_float_t = typename BasicJsonType::number_float_t;
    using string_t = typename BasicJsonType::string_t;
    using binary_t = typename BasicJsonType::binary_t;

    /*!
    @brief a null value was read
    @return whether parsing should proceed
    */
    virtual bool null() = 0;

    /*!
    @brief a boolean value was read
    @param[in] val  boolean value
    @return whether parsing should proceed
    */
    virtual bool boolean(bool val) = 0;

    /*!
    @brief an integer number was read
    @param[in] val  integer value
    @return whether parsing should proceed
    */
    virtual bool number_integer(number_integer_t val) = 0;

    /*!
    @brief an unsigned integer number was read
    @param[in] val  unsigned integer value
    @return whether parsing should proceed
    */
    virtual bool number_unsigned(number_unsigned_t val) = 0;

    /*!
    @brief a floating-point number was read
    @param[in] val  floating-point value
    @param[in] s    raw token value
    @return whether parsing should proceed
    */
    virtual bool number_float(number_float_t val, const string_t &s) = 0;

    /*!
    @brief a string value was read
    @param[in] val  string value
    @return whether parsing should proceed
    @note It is safe to move the passed string value.
    */
    virtual bool string(string_t &val) = 0;

    /*!
    @brief a binary value was read
    @param[in] val  binary value
    @return whether parsing should proceed
    @note It is safe to move the passed binary value.
    */
    virtual bool binary(binary_t &val) = 0;

    /*!
    @brief the beginning of an object was read
    @param[in] elements  number of object elements or -1 if unknown
    @return whether parsing should proceed
    @note binary formats may report the number of elements
    */
    virtual bool start_object(std::size_t elements) = 0;

    /*!
    @brief an object key was read
    @param[in] val  object key
    @return whether parsing should proceed
    @note It is safe to move the passed string.
    */
    virtual bool key(string_t &val) = 0;

    /*!
    @brief the end of an object was read
    @return whether parsing should proceed
    */
    virtual bool end_object() = 0;

    /*!
    @brief the beginning of an array was read
    @param[in] elements  number of array elements or -1 if unknown
    @return whether parsing should proceed
    @note binary formats may report the number of elements
    */
    virtual bool start_array(std::size_t elements) = 0;

    /*!
    @brief the end of an array was read
    @return whether parsing should proceed
    */
    virtual bool end_array() = 0;

    /*!
    @brief a parse error occurred
    @param[in] position    the position in the input where the error occurs
    @param[in] last_token  the last read token
    @param[in] ex          an exception object describing the error
    @return whether parsing should proceed (must return false)
    */
    virtual bool parse_error(std::size_t position,
                             const std::string &last_token,
                             const detail::exception &ex) = 0;

    json_sax() = default;
    json_sax(const json_sax &) = default;
    json_sax(json_sax &&) noexcept = default;
    json_sax &operator=(const json_sax &) = default;
    json_sax &operator=(json_sax &&) noexcept = default;
    virtual ~json_sax() = default;
};


namespace detail {
    /*!
@brief SAX implementation to create a JSON value from SAX events

This class implements the @ref json_sax interface and processes the SAX events
to create a JSON value which makes it basically a DOM parser. The structure or
hierarchy of the JSON value is managed by the stack `ref_stack` which contains
a pointer to the respective array or object for each recursion depth.

After successful parsing, the value that is passed by reference to the
constructor contains the parsed value.

@tparam BasicJsonType  the JSON type
*/
    template<typename BasicJsonType>
    class json_sax_dom_parser {
    public:
        using number_integer_t = typename BasicJsonType::number_integer_t;
        using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
        using number_float_t = typename BasicJsonType::number_float_t;
        using string_t = typename BasicJsonType::string_t;
        using binary_t = typename BasicJsonType::binary_t;

        /*!
    @param[in,out] r  reference to a JSON value that is manipulated while
                       parsing
    @param[in] allow_exceptions_  whether parse errors yield exceptions
    */
        explicit json_sax_dom_parser(BasicJsonType &r, const bool allow_exceptions_ = true)
            : root(r), allow_exceptions(allow_exceptions_) {}

        // make class move-only
        json_sax_dom_parser(const json_sax_dom_parser &) = delete;
        json_sax_dom_parser(json_sax_dom_parser &&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
        json_sax_dom_parser &operator=(const json_sax_dom_parser &) = delete;
        json_sax_dom_parser &operator=(json_sax_dom_parser &&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
        ~json_sax_dom_parser() = default;

        bool null() {
            handle_value(nullptr);
            return true;
        }

        bool boolean(bool val) {
            handle_value(val);
            return true;
        }

        bool number_integer(number_integer_t val) {
            handle_value(val);
            return true;
        }

        bool number_unsigned(number_unsigned_t val) {
            handle_value(val);
            return true;
        }

        bool number_float(number_float_t val, const string_t & /*unused*/) {
            handle_value(val);
            return true;
        }

        bool string(string_t &val) {
            handle_value(val);
            return true;
        }

        bool binary(binary_t &val) {
            handle_value(std::move(val));
            return true;
        }

        bool start_object(std::size_t len) {
            ref_stack.push_back(handle_value(BasicJsonType::value_t::object));

            if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) {
                JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
            }

            return true;
        }

        bool key(string_t &val) {
            JSON_ASSERT(!ref_stack.empty());
            JSON_ASSERT(ref_stack.back()->is_object());

            // add null at given key and store the reference for later
            object_element = &(ref_stack.back()->m_value.object->operator[](val));
            return true;
        }

        bool end_object() {
            JSON_ASSERT(!ref_stack.empty());
            JSON_ASSERT(ref_stack.back()->is_object());

            ref_stack.back()->set_parents();
            ref_stack.pop_back();
            return true;
        }

        bool start_array(std::size_t len) {
            ref_stack.push_back(handle_value(BasicJsonType::value_t::array));

            if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) {
                JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
            }

            return true;
        }

        bool end_array() {
            JSON_ASSERT(!ref_stack.empty());
            JSON_ASSERT(ref_stack.back()->is_array());

            ref_stack.back()->set_parents();
            ref_stack.pop_back();
            return true;
        }

        template<class Exception>
        bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/,
                         const Exception &ex) {
            errored = true;
            static_cast<void>(ex);
            if (allow_exceptions) {
                JSON_THROW(ex);
            }
            return false;
        }

        constexpr bool is_errored() const {
            return errored;
        }

    private:
        /*!
    @invariant If the ref stack is empty, then the passed value will be the new
               root.
    @invariant If the ref stack contains a value, then it is an array or an
               object to which we can add elements
    */
        template<typename Value>
        JSON_HEDLEY_RETURNS_NON_NULL
                BasicJsonType *
                handle_value(Value &&v) {
            if (ref_stack.empty()) {
                root = BasicJsonType(std::forward<Value>(v));
                return &root;
            }

            JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());

            if (ref_stack.back()->is_array()) {
                ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));
                return &(ref_stack.back()->m_value.array->back());
            }

            JSON_ASSERT(ref_stack.back()->is_object());
            JSON_ASSERT(object_element);
            *object_element = BasicJsonType(std::forward<Value>(v));
            return object_element;
        }

        /// the parsed JSON value
        BasicJsonType &root;
        /// stack to model hierarchy of values
        std::vector<BasicJsonType *> ref_stack{};
        /// helper to hold the reference for the next object element
        BasicJsonType *object_element = nullptr;
        /// whether a syntax error occurred
        bool errored = false;
        /// whether to throw exceptions in case of errors
        const bool allow_exceptions = true;
    };

    template<typename BasicJsonType>
    class json_sax_dom_callback_parser {
    public:
        using number_integer_t = typename BasicJsonType::number_integer_t;
        using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
        using number_float_t = typename BasicJsonType::number_float_t;
        using string_t = typename BasicJsonType::string_t;
        using binary_t = typename BasicJsonType::binary_t;
        using parser_callback_t = typename BasicJsonType::parser_callback_t;
        using parse_event_t = typename BasicJsonType::parse_event_t;

        json_sax_dom_callback_parser(BasicJsonType &r,
                                     const parser_callback_t cb,
                                     const bool allow_exceptions_ = true)
            : root(r), callback(cb), allow_exceptions(allow_exceptions_) {
            keep_stack.push_back(true);
        }

        // make class move-only
        json_sax_dom_callback_parser(const json_sax_dom_callback_parser &) = delete;
        json_sax_dom_callback_parser(json_sax_dom_callback_parser &&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
        json_sax_dom_callback_parser &operator=(const json_sax_dom_callback_parser &) = delete;
        json_sax_dom_callback_parser &operator=(json_sax_dom_callback_parser &&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
        ~json_sax_dom_callback_parser() = default;

        bool null() {
            handle_value(nullptr);
            return true;
        }

        bool boolean(bool val) {
            handle_value(val);
            return true;
        }

        bool number_integer(number_integer_t val) {
            handle_value(val);
            return true;
        }

        bool number_unsigned(number_unsigned_t val) {
            handle_value(val);
            return true;
        }

        bool number_float(number_float_t val, const string_t & /*unused*/) {
            handle_value(val);
            return true;
        }

        bool string(string_t &val) {
            handle_value(val);
            return true;
        }

        bool binary(binary_t &val) {
            handle_value(std::move(val));
            return true;
        }

        bool start_object(std::size_t len) {
            // check callback for object start
            const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
            keep_stack.push_back(keep);

            auto val = handle_value(BasicJsonType::value_t::object, true);
            ref_stack.push_back(val.second);

            // check object limit
            if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) {
                JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
            }

            return true;
        }

        bool key(string_t &val) {
            BasicJsonType k = BasicJsonType(val);

            // check callback for key
            const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
            key_keep_stack.push_back(keep);

            // add discarded value at given key and store the reference for later
            if (keep && ref_stack.back()) {
                object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded);
            }

            return true;
        }

        bool end_object() {
            if (ref_stack.back()) {
                if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) {
                    // discard object
                    *ref_stack.back() = discarded;
                } else {
                    ref_stack.back()->set_parents();
                }
            }

            JSON_ASSERT(!ref_stack.empty());
            JSON_ASSERT(!keep_stack.empty());
            ref_stack.pop_back();
            keep_stack.pop_back();

            if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) {
                // remove discarded value
                for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) {
                    if (it->is_discarded()) {
                        ref_stack.back()->erase(it);
                        break;
                    }
                }
            }

            return true;
        }

        bool start_array(std::size_t len) {
            const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
            keep_stack.push_back(keep);

            auto val = handle_value(BasicJsonType::value_t::array, true);
            ref_stack.push_back(val.second);

            // check array limit
            if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size())) {
                JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
            }

            return true;
        }

        bool end_array() {
            bool keep = true;

            if (ref_stack.back()) {
                keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
                if (keep) {
                    ref_stack.back()->set_parents();
                } else {
                    // discard array
                    *ref_stack.back() = discarded;
                }
            }

            JSON_ASSERT(!ref_stack.empty());
            JSON_ASSERT(!keep_stack.empty());
            ref_stack.pop_back();
            keep_stack.pop_back();

            // remove discarded value
            if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) {
                ref_stack.back()->m_value.array->pop_back();
            }

            return true;
        }

        template<class Exception>
        bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/,
                         const Exception &ex) {
            errored = true;
            static_cast<void>(ex);
            if (allow_exceptions) {
                JSON_THROW(ex);
            }
            return false;
        }

        constexpr bool is_errored() const {
            return errored;
        }

    private:
        /*!
    @param[in] v  value to add to the JSON value we build during parsing
    @param[in] skip_callback  whether we should skip calling the callback
               function; this is required after start_array() and
               start_object() SAX events, because otherwise we would call the
               callback function with an empty array or object, respectively.

    @invariant If the ref stack is empty, then the passed value will be the new
               root.
    @invariant If the ref stack contains a value, then it is an array or an
               object to which we can add elements

    @return pair of boolean (whether value should be kept) and pointer (to the
            passed value in the ref_stack hierarchy; nullptr if not kept)
    */
        template<typename Value>
        std::pair<bool, BasicJsonType *> handle_value(Value &&v, const bool skip_callback = false) {
            JSON_ASSERT(!keep_stack.empty());

            // do not handle this value if we know it would be added to a discarded
            // container
            if (!keep_stack.back()) {
                return {false, nullptr};
            }

            // create value
            auto value = BasicJsonType(std::forward<Value>(v));

            // check callback
            const bool keep = skip_callback || callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);

            // do not handle this value if we just learnt it shall be discarded
            if (!keep) {
                return {false, nullptr};
            }

            if (ref_stack.empty()) {
                root = std::move(value);
                return {true, &root};
            }

            // skip this value if we already decided to skip the parent
            // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)
            if (!ref_stack.back()) {
                return {false, nullptr};
            }

            // we now only expect arrays and objects
            JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());

            // array
            if (ref_stack.back()->is_array()) {
                ref_stack.back()->m_value.array->emplace_back(std::move(value));
                return {true, &(ref_stack.back()->m_value.array->back())};
            }

            // object
            JSON_ASSERT(ref_stack.back()->is_object());
            // check if we should store an element for the current key
            JSON_ASSERT(!key_keep_stack.empty());
            const bool store_element = key_keep_stack.back();
            key_keep_stack.pop_back();

            if (!store_element) {
                return {false, nullptr};
            }

            JSON_ASSERT(object_element);
            *object_element = std::move(value);
            return {true, object_element};
        }

        /// the parsed JSON value
        BasicJsonType &root;
        /// stack to model hierarchy of values
        std::vector<BasicJsonType *> ref_stack{};
        /// stack to manage which values to keep
        std::vector<bool> keep_stack{};
        /// stack to manage which object keys to keep
        std::vector<bool> key_keep_stack{};
        /// helper to hold the reference for the next object element
        BasicJsonType *object_element = nullptr;
        /// whether a syntax error occurred
        bool errored = false;
        /// callback function
        const parser_callback_t callback = nullptr;
        /// whether to throw exceptions in case of errors
        const bool allow_exceptions = true;
        /// a discarded value for the callback
        BasicJsonType discarded = BasicJsonType::value_t::discarded;
    };

    template<typename BasicJsonType>
    class json_sax_acceptor {
    public:
        using number_integer_t = typename BasicJsonType::number_integer_t;
        using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
        using number_float_t = typename BasicJsonType::number_float_t;
        using string_t = typename BasicJsonType::string_t;
        using binary_t = typename BasicJsonType::binary_t;

        bool null() {
            return true;
        }

        bool boolean(bool /*unused*/) {
            return true;
        }

        bool number_integer(number_integer_t /*unused*/) {
            return true;
        }

        bool number_unsigned(number_unsigned_t /*unused*/) {
            return true;
        }

        bool number_float(number_float_t /*unused*/, const string_t & /*unused*/) {
            return true;
        }

        bool string(string_t & /*unused*/) {
            return true;
        }

        bool binary(binary_t & /*unused*/) {
            return true;
        }

        bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) {
            return true;
        }

        bool key(string_t & /*unused*/) {
            return true;
        }

        bool end_object() {
            return true;
        }

        bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) {
            return true;
        }

        bool end_array() {
            return true;
        }

        bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/, const detail::exception & /*unused*/) {
            return false;
        }
    };

} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
